import { loadShaderFromFile, loadImgFromUrl } from "../../lib/js/tool-utils.js";
import webglUtils from "../../lib/js/webgl-utils.js";

const main = async () => {
  const vs = await loadShaderFromFile("./glsl/进一步图像.vert");
  const fs = await loadShaderFromFile("./glsl/进一步图像.frag");
  const image = await loadImgFromUrl(
    "https://webgl2fundamentals.org/webgl/resources/leaves.jpg"
  );
  render(image, vs, fs);
};

main();
function render(image, vertexShaderSource, fragmentShaderSource) {
  const canvas = document.querySelector("#canvas");
  const gl = canvas.getContext("webgl2");
  if (!gl) {
    return;
  }

  const program = webglUtils.createProgramFromSources(gl, [
    vertexShaderSource,
    fragmentShaderSource,
  ]);

  const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
  const texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");

  const resolutionLocation = gl.getUniformLocation(program, "u_resolution");
  const imageLocation = gl.getUniformLocation(program, "u_image");
  const kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
  const kernelWeightLocation = gl.getUniformLocation(program, "u_kernelWeight");
  const flipYLocation = gl.getUniformLocation(program, "u_flipY");

  const vao = gl.createVertexArray();

  gl.bindVertexArray(vao);

  const positionBuffer = gl.createBuffer();

  gl.enableVertexAttribArray(positionAttributeLocation);

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  let size = 2; // 2 components per iteration
  let type = gl.FLOAT; // the data is 32bit floats
  let normalize = false; // don't normalize the data
  let stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  let offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    positionAttributeLocation,
    size,
    type,
    normalize,
    stride,
    offset
  );

  // 提供矩形的纹理坐标。
  const texCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([
      0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0,
    ]),
    gl.STATIC_DRAW
  );

  // 启用
  gl.enableVertexAttribArray(texCoordAttributeLocation);

  size = 2; // 2 components per iteration
  type = gl.FLOAT; // the data is 32bit floats
  normalize = false; // don't normalize the data
  stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  offset = 0; // start at the beginning of the buffer
  gl.vertexAttribPointer(
    texCoordAttributeLocation,
    size,
    type,
    normalize,
    stride,
    offset
  );

  function createAndSetupTexture(gl) {
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // 设置纹理，这样我们可以对任意大小的图像进行像素操作
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    return texture;
  }
  // 创建一个纹理并写入图像
  const originalImageTexture = createAndSetupTexture(gl);

  // 上传图像到这个纹理中
  const mipLevel = 0; // 指定详细级别
  const internalFormat = gl.RGBA; // 指定纹理中的颜色类比
  const srcFormat = gl.RGBA; // 指定纹理的数据格式
  const srcType = gl.UNSIGNED_BYTE; // 指定纹理的数据类型
  gl.texImage2D(
    gl.TEXTURE_2D,
    mipLevel,
    internalFormat,
    srcFormat,
    srcType,
    image
  );

  // 创建两个纹理绑定到帧缓冲
  const textures = [];
  const framebuffers = [];
  for (let ii = 0; ii < 2; ++ii) {
    const texture = createAndSetupTexture(gl);
    textures.push(texture);

    // 设置纹理大小和图像大小一致
    const mipLevel = 0; // the largest mip
    const internalFormat = gl.RGBA; // format we want in the texture
    const border = 0; // 必须为 0
    const srcFormat = gl.RGBA; // format of data we are supplying
    const srcType = gl.UNSIGNED_BYTE; // 指定纹理的数据类型
    const data = null; // data没有值意味着创建一个空白的纹理
    gl.texImage2D(
      gl.TEXTURE_2D,
      mipLevel,
      internalFormat,
      image.width,
      image.height,
      border,
      srcFormat,
      srcType,
      data
    );

    // 创建一个帧缓冲
    const fbo = gl.createFramebuffer();
    framebuffers.push(fbo);
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

    // 绑定纹理到帧缓冲
    const attachmentPoint = gl.COLOR_ATTACHMENT0;
    gl.framebufferTexture2D(
      gl.FRAMEBUFFER,
      attachmentPoint,
      gl.TEXTURE_2D,
      texture,
      mipLevel
    );
  }

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // 设置一个与图像大小相同的矩形。
  setRectangle(gl, 0, 0, image.width, image.height);

  // 定义一些卷积核
  const kernels = {
    normal: [0, 0, 0, 0, 1, 0, 0, 0, 0],
    gaussianBlur: [
      0.045, 0.122, 0.045, 0.122, 0.332, 0.122, 0.045, 0.122, 0.045,
    ],
    gaussianBlur2: [1, 2, 1, 2, 4, 2, 1, 2, 1],
    gaussianBlur3: [0, 1, 0, 1, 1, 1, 0, 1, 0],
    unsharpen: [-1, -1, -1, -1, 9, -1, -1, -1, -1],
    sharpness: [0, -1, 0, -1, 5, -1, 0, -1, 0],
    sharpen: [-1, -1, -1, -1, 16, -1, -1, -1, -1],
    edgeDetect: [
      -0.125, -0.125, -0.125, -0.125, 1, -0.125, -0.125, -0.125, -0.125,
    ],
    edgeDetect2: [-1, -1, -1, -1, 8, -1, -1, -1, -1],
    edgeDetect3: [-5, 0, 0, 0, 0, 0, 0, 0, 5],
    edgeDetect4: [-1, -1, -1, 0, 0, 0, 1, 1, 1],
    edgeDetect5: [-1, -1, -1, 2, 2, 2, -1, -1, -1],
    edgeDetect6: [-5, -5, -5, -5, 39, -5, -5, -5, -5],
    sobelHorizontal: [1, 2, 1, 0, 0, 0, -1, -2, -1],
    sobelVertical: [1, 0, -1, 2, 0, -2, 1, 0, -1],
    previtHorizontal: [1, 1, 1, 0, 0, 0, -1, -1, -1],
    previtVertical: [1, 0, -1, 1, 0, -1, 1, 0, -1],
    boxBlur: [0.111, 0.111, 0.111, 0.111, 0.111, 0.111, 0.111, 0.111, 0.111],
    triangleBlur: [
      0.0625, 0.125, 0.0625, 0.125, 0.25, 0.125, 0.0625, 0.125, 0.0625,
    ],
    emboss: [-2, -1, 0, -1, 1, 1, 0, 1, 2],
  };

  // 将要使用的效果列表
  const effects = [
    { name: "normal", on: true },
    { name: "gaussianBlur" },
    { name: "gaussianBlur2", on: true },
    { name: "gaussianBlur3", on: true },
    { name: "unsharpen" },
    { name: "sharpness" },
    { name: "sharpen" },
    { name: "edgeDetect" },
    { name: "edgeDetect2" },
    { name: "edgeDetect3" },
    { name: "edgeDetect4" },
    { name: "edgeDetect5" },
    { name: "edgeDetect6" },
    { name: "sobelHorizontal" },
    { name: "sobelVertical" },
    { name: "previtHorizontal" },
    { name: "previtVertical" },
    { name: "boxBlur" },
    { name: "triangleBlur" },
    { name: "emboss" },
  ];
  // Setup a ui.
  const ui = document.querySelector("#ui");
  const table = document.createElement("table");
  const tbody = document.createElement("tbody");
  for (let ii = 0; ii < effects.length; ++ii) {
    const effect = effects[ii];
    const tr = document.createElement("tr");
    const td = document.createElement("td");
    const chk = document.createElement("input");
    chk.value = effect.name;
    chk.type = "checkbox";
    if (effect.on) {
      chk.checked = "true";
    }
    chk.onchange = drawEffects;
    td.appendChild(chk);
    td.appendChild(document.createTextNode(effect.name));
    tr.appendChild(td);
    tbody.appendChild(tr);
  }
  table.appendChild(tbody);
  ui.appendChild(table);
  $("#ui table").tableDnD({ onDrop: drawEffects });

  drawEffects();

  function computeKernelWeight(kernel) {
    const weight = kernel.reduce(function (prev, curr) {
      return prev + curr;
    });
    return weight <= 0 ? 1 : weight;
  }

  function drawEffects() {
    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // Tell WebGL how to convert from clip space to pixels
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    // Clear the canvas
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // 使用着色器对
    gl.useProgram(program);

    // 绑定属性/缓冲帧
    gl.bindVertexArray(vao);

    // 从单元 0 上的原始图像开始
    gl.activeTexture(gl.TEXTURE0 + 0);
    gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);

    // 告诉着色器从纹理单元 0 获取纹理
    gl.uniform1i(imageLocation, 0);

    // 在绘制纹理时不要翻转图像
    gl.uniform1f(flipYLocation, 1);

    // 循环遍历我们想要应用的每个效果。
    let count = 0;
    for (let ii = 0; ii < tbody.rows.length; ++ii) {
      const checkbox = tbody.rows[ii].firstChild.firstChild;
      if (checkbox.checked) {
        // 在一个缓冲帧上设置绘制信息
        setFramebuffer(framebuffers[count % 2], image.width, image.height);

        drawWithKernel(checkbox.value);

        // 对于下一次绘制，使用我们刚刚渲染的纹理。
        gl.bindTexture(gl.TEXTURE_2D, textures[count % 2]);

        // 增加计数，以便我们下次使用其他纹理。
        ++count;
      }
    }

    // 最后将结果绘制到画布上
    gl.uniform1f(flipYLocation, -1); // 需要翻转画布

    setFramebuffer(null, gl.canvas.width, gl.canvas.height);

    // Clear the canvas
    gl.clearColor(0, 0, 0, 0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    drawWithKernel("normal");
  }

  function setFramebuffer(fbo, width, height) {
    // 绑定需要渲染的缓冲帧
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

    // 告诉着色器帧缓冲区的分辨率
    gl.uniform2f(resolutionLocation, width, height);

    // 告诉 WebGL 如何从裁剪空间转换为像素
    gl.viewport(0, 0, width, height);
  }

  function drawWithKernel(name) {
    // 设置内核和它的权重
    gl.uniform1fv(kernelLocation, kernels[name]);
    gl.uniform1f(kernelWeightLocation, computeKernelWeight(kernels[name]));

    // 绘制矩形。
    const primitiveType = gl.TRIANGLES;
    const offset = 0;
    const count = 6;
    gl.drawArrays(primitiveType, offset, count);
  }
}

function setRectangle(gl, x, y, width, height) {
  const x1 = x;
  const x2 = x + width;
  const y1 = y;
  const y2 = y + height;
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([x1, y1, x2, y1, x1, y2, x1, y2, x2, y1, x2, y2]),
    gl.STATIC_DRAW
  );
}
